package user

import (
	"crypto/sha256"
	"database/sql"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"strings"

	"github.com/alecthomas/log4go"
	"github.com/google/uuid"
	"pac/client"
	"pac/conf"
	"pac/db"
	"pac/device"
	"pac/proto"
)

func genUserID(username string) string {
	u := uuid.NewSHA1(uuid.NameSpaceOID, []byte(username))
	buf := [32]byte{}
	hex.Encode(buf[:], u[:])
	return string(buf[:])
}

func shaPasswd(str string) string {
	h := sha256.New()
	h.Write([]byte(str))
	bs := h.Sum(nil)
	buf := make([]byte, len(bs)*2)
	hex.Encode(buf, bs)
	return fmt.Sprintf("%x", bs)
}

func init() {
	registerPbMsgAndProcessor(pb.Cmd_REGISTER, Req, (*pb.RegisterReq)(nil), procRegister)
	registerPbMsgAndProcessor(pb.Cmd_REGISTER, Rsp, (*pb.RegisterRsp)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_LOGIN, Req, (*pb.LoginReq)(nil), procLogin)
	registerPbMsgAndProcessor(pb.Cmd_LOGIN, Rsp, (*pb.LoginRsp)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_CHANGE_PASSWORD, Req, (*pb.ChangePasswordReq)(nil), procChangePasswd)
	registerPbMsgAndProcessor(pb.Cmd_CHANGE_PASSWORD, Rsp, (*pb.ChangePasswordRsp)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_QUERY_DEVICE_LIST, Req, (*pb.QueryDeviceListReq)(nil), procQueryDeviceList)
	registerPbMsgAndProcessor(pb.Cmd_QUERY_DEVICE_LIST, Rsp, (*pb.QueryDeviceListRsp)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_ON_DEVICE_ONLINE_STATUS, Req, (*pb.OnDeviceOnlineStatusReq)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_ON_DEVICE_ONLINE_STATUS, Rsp, (*pb.OnDeviceOnlineStatusRsp)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_REPORT_MEASURE, Req, (*pb.ReportMeasureReq)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_REPORT_MEASURE, Rsp, (*pb.ReportMeasureRsp)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_REPORT_WAVE, Req, (*pb.ReportWaveReq)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_REPORT_WAVE, Rsp, (*pb.ReportWaveRsp)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_QUERY_MEASURE, Req, (*pb.QueryMeasureReq)(nil), procQueryMeasure)
	registerPbMsgAndProcessor(pb.Cmd_QUERY_MEASURE, Rsp, (*pb.QueryMeasureRsp)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_QUERY_WAVE, Req, (*pb.QueryWaveReq)(nil), procQueryWave)
	registerPbMsgAndProcessor(pb.Cmd_QUERY_WAVE, Rsp, (*pb.QueryWaveRsp)(nil), nil)
	registerPbMsgAndProcessor(pb.Cmd_CTRL_ON_OFF, Req, (*pb.CtrlOnOffReq)(nil), procCtrlOnOff)
	registerPbMsgAndProcessor(pb.Cmd_CTRL_ON_OFF, Rsp, (*pb.CtrlOnOffRsp)(nil), nil)
}

func procRegister(cli *Client, pkt *Packet) {
	reqMsg := pkt.Pb.(*pb.RegisterReq)
	rspMsg := &pb.RegisterRsp{}

	rspMsg.ErrCode, rspMsg.UserId = doRegister(reqMsg)

	err := cli.SendRsp(pkt.Seq, rspMsg)
	if err != nil {
		log4go.Error(err)
	}
}

func doRegister(reqMsg *pb.RegisterReq) (errCode pb.ErrCode, userID string) {
	if len(reqMsg.GetUsername()) == 0 {
		log4go.Info("Username is empty")
		return pb.ErrCode_INVALID_PARAM, ""
	}
	if len(reqMsg.GetPasswd()) == 0 {
		log4go.Info("Passwd is empty")
		return pb.ErrCode_INVALID_PARAM, ""
	}

	if conf.DBAvailable {

		userID = genUserID(reqMsg.GetUsername())
		passwd := shaPasswd(reqMsg.GetPasswd())

		db := database.GetDb()
		_, err := db.Exec("INSERT INTO user(user_id, username, passwd) VALUES(?,?,?)", userID, reqMsg.GetUsername(), passwd)
		if err != nil {
			if strings.Contains(err.Error(), "Duplicate entry") {
				log4go.Info("%v already exists", reqMsg.GetUsername())
				return pb.ErrCode_ALREADY_EXISTS, ""
			}
			log4go.Error(err)
			return pb.ErrCode_FAILURE, ""
		}

	}

	return pb.ErrCode_SUCCESS, userID
}

func procLogin(cli *Client, pkt *Packet) {
	reqMsg := pkt.Pb.(*pb.LoginReq)
	rspMsg := &pb.LoginRsp{}

	rspMsg.ErrCode, rspMsg.UserId = doLogin(reqMsg)

	if rspMsg.ErrCode == pb.ErrCode_SUCCESS {
		cli.userID = rspMsg.UserId
		cli.Username = reqMsg.GetUsername()
		client.PutClient(cli)
	}

	err := cli.SendRsp(pkt.Seq, rspMsg)
	if err != nil {
		log4go.Error(err)
	}
}

func doLogin(reqMsg *pb.LoginReq) (errCode pb.ErrCode, userID string) {
	if len(reqMsg.GetUsername()) == 0 {
		log4go.Info("Username is empty")
		return pb.ErrCode_INVALID_PARAM, ""
	}
	if len(reqMsg.GetPasswd()) == 0 {
		log4go.Info("Passwd is empty")
		return pb.ErrCode_INVALID_PARAM, ""
	}

	if conf.DBAvailable {

		passwd := shaPasswd(reqMsg.GetPasswd())

		db := database.GetDb()
		err := db.QueryRow("SELECT user_id FROM user WHERE username=? AND passwd=?", reqMsg.GetUsername(), passwd).Scan(&userID)
		if err != nil {
			if err != sql.ErrNoRows {
				return pb.ErrCode_PASSWORD_ERROR, ""
			}
			log4go.Error(err)
			return pb.ErrCode_FAILURE, ""
		}

	} else {
		userID = reqMsg.GetUsername()
	}

	return pb.ErrCode_SUCCESS, userID
}

func procChangePasswd(cli *Client, pkt *Packet) {
	reqMsg := pkt.Pb.(*pb.ChangePasswordReq)
	rspMsg := &pb.ChangePasswordRsp{}

	rspMsg.ErrCode = doChangePasswd(reqMsg)

	err := cli.SendRsp(pkt.Seq, rspMsg)
	if err != nil {
		log4go.Error(err)
	}
}

func doChangePasswd(reqMsg *pb.ChangePasswordReq) (errCode pb.ErrCode) {
	if len(reqMsg.GetUsername()) == 0 {
		log4go.Info("Username is empty")
		return pb.ErrCode_INVALID_PARAM
	}
	if len(reqMsg.GetOldPwd()) == 0 {
		log4go.Info("Old passwd is empty")
		return pb.ErrCode_INVALID_PARAM
	}
	if len(reqMsg.GetNewPwd()) == 0 {
		log4go.Info("New Passwd is empty")
		return pb.ErrCode_INVALID_PARAM
	}

	if conf.DBAvailable {

		oldPasswd := shaPasswd(reqMsg.GetOldPwd())

		var userID string

		db := database.GetDb()
		err := db.QueryRow("SELECT user_id FROM user WHERE username=? AND passwd=?", reqMsg.GetUsername(), oldPasswd).Scan(&userID)
		if err != nil {
			if err != sql.ErrNoRows {
				return pb.ErrCode_PASSWORD_ERROR
			}
			log4go.Error(err)
			return pb.ErrCode_FAILURE
		}

		newPasswd := shaPasswd(reqMsg.GetNewPwd())
		_, err = db.Exec("UPDATE user SET passwd=? WHERE user_id=?", newPasswd, userID)
		if err != nil {
			log4go.Error(err)
			return pb.ErrCode_FAILURE
		}

	}

	return pb.ErrCode_SUCCESS
}

func procQueryDeviceList(cli *Client, pkt *Packet) {
	reqMsg := pkt.Pb.(*pb.QueryDeviceListReq)
	rspMsg := &pb.QueryDeviceListRsp{}

	rspMsg.ErrCode, rspMsg.Devices = doQueryDeviceList(cli, reqMsg)

	err := cli.SendRsp(pkt.Seq, rspMsg)
	if err != nil {
		log4go.Error(err)
	}
}

func doQueryDeviceList(cli *Client, reqMsg *pb.QueryDeviceListReq) (errCode pb.ErrCode, devices []*pb.QueryDeviceListRspDeviceInfo) {
	var userID = reqMsg.GetUserId()
	if len(userID) == 0 {
		userID = cli.GetID()
	}

	if conf.DBAvailable {

		db := database.GetDb()
		rows, err := db.Query("SELECT device_id,address FROM device")
		if err != nil {
			log4go.Error(err)
			return pb.ErrCode_FAILURE, nil
		}

		for rows.Next() {
			var deviceID, address sql.NullString
			err := rows.Scan(&deviceID, &address)
			if err != nil {
				log4go.Error(err)
				return pb.ErrCode_FAILURE, nil
			}
			devices = append(devices, &pb.QueryDeviceListRspDeviceInfo{
				DeviceId: deviceID.String,
				Address:  address.String,
				Online:   client.FindClient(deviceID.String) != nil,
			})
		}

	} else {
		for _, devCli := range client.GetAllClient(client.Device) {
			devices = append(devices, &pb.QueryDeviceListRspDeviceInfo{
				DeviceId: devCli.GetID(),
				Online:   true,
			})
		}
	}

	return pb.ErrCode_SUCCESS, devices
}

func procQueryMeasure(cli *Client, pkt *Packet) {
	reqMsg := pkt.Pb.(*pb.QueryMeasureReq)
	rspMsg := &pb.QueryMeasureRsp{}

	rspMsg.ErrCode, rspMsg.MeasureData = doQueryMeasure(reqMsg)

	err := cli.SendRsp(pkt.Seq, rspMsg)
	if err != nil {
		log4go.Error(err)
	}
}

func doQueryMeasure(reqMsg *pb.QueryMeasureReq) (errCode pb.ErrCode, data *pb.MeasureData) {
	if len(reqMsg.GetDeviceId()) == 0 {
		log4go.Info("DeviceId is empty")
		errCode = pb.ErrCode_INVALID_PARAM
		return
	}

	data = &pb.MeasureData{}

	db := database.GetDb()
	err := db.QueryRow("SELECT voltage,current,active_power,reactive_power,electricity,UNIX_TIMESTAMP(inserttime) FROM device_measure WHERE device_id=? ORDER BY inserttime DESC LIMIT 1", reqMsg.GetDeviceId()).
		Scan(&data.Voltage, &data.Current, &data.ActivePower, &data.ReactivePower, &data.ElectricEnergy, &data.Timestamp)
	if err != nil {
		if err != sql.ErrNoRows {
			log4go.Error(err)
			return pb.ErrCode_FAILURE, nil
		}
	}

	errCode = pb.ErrCode_SUCCESS
	return
}

func procQueryWave(cli *Client, pkt *Packet) {
	reqMsg := pkt.Pb.(*pb.QueryWaveReq)
	rspMsg := &pb.QueryWaveRsp{}

	rspMsg.ErrCode, rspMsg.WaveData = doQueryWave(reqMsg)

	err := cli.SendRsp(pkt.Seq, rspMsg)
	if err != nil {
		log4go.Error(err)
	}
}

func doQueryWave(reqMsg *pb.QueryWaveReq) (errCode pb.ErrCode, data *pb.WaveData) {
	if len(reqMsg.GetDeviceId()) == 0 {
		log4go.Info("DeviceId is empty")
		errCode = pb.ErrCode_INVALID_PARAM
		return
	}

	var (
		uRaw, iRaw []byte
		timestamp  sql.NullInt64
	)

	db := database.GetDb()
	err := db.QueryRow("SELECT wave_voltage,wave_current,UNIX_TIMESTAMP(inserttime) FROM device_wave WHERE device_id=? ORDER BY inserttime DESC LIMIT 1", reqMsg.GetDeviceId()).
		Scan(&uRaw, &iRaw, &timestamp)
	if err != nil {
		if err != sql.ErrNoRows {
			log4go.Error(err)
			errCode = pb.ErrCode_FAILURE
			return
		}
	}

	data = &pb.WaveData{
		Timestamp: timestamp.Int64,
		Voltage:   make([]float32, len(uRaw)/2),
		Current:   make([]float32, len(iRaw)/2),
	}

	n := len(uRaw) / 2
	for i := 0; i < n; i++ {
		data.Voltage[i] = float32(int16(binary.LittleEndian.Uint16(uRaw[i*2:])))
	}

	n = len(iRaw) / 2
	for i := 0; i < n; i++ {
		data.Current[i] = float32(int16(binary.LittleEndian.Uint16(iRaw[i*2:])))
	}

	errCode = pb.ErrCode_SUCCESS
	return
}

func procCtrlOnOff(cli *Client, pkt *Packet) {
	reqMsg := pkt.Pb.(*pb.CtrlOnOffReq)
	rspMsg := &pb.CtrlOnOffRsp{}

	rspMsg.ErrCode = doCtrlOnOff(reqMsg)

	err := cli.SendRsp(pkt.Seq, rspMsg)
	if err != nil {
		log4go.Error(err)
	}
}

func doCtrlOnOff(reqMsg *pb.CtrlOnOffReq) (errCode pb.ErrCode) {
	//if len(reqMsg.GetDeviceId()) == 0 {
	//	log4go.Info("DeviceId is empty")
	//	return pb.ErrCode_INVALID_PARAM
	//}

	return device.CtrlOnOff(reqMsg.GetDeviceId(), reqMsg.GetOn())
}
